 /*******************************************************************************
  * Copyright (c) 2005 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.dialogs;

 import java.util.ArrayList ;

 import org.eclipse.jface.viewers.CheckStateChangedEvent;
 import org.eclipse.jface.viewers.CheckboxTreeViewer;
 import org.eclipse.jface.viewers.ICheckStateListener;
 import org.eclipse.jface.viewers.ITreeViewerListener;
 import org.eclipse.jface.viewers.TreeExpansionEvent;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Item;
 import org.eclipse.swt.widgets.Tree;
 import org.eclipse.swt.widgets.TreeItem;
 import org.eclipse.swt.widgets.Widget;

 /**
  * CheckboxTreeViewer with special behaviour of the checked / gray state on
  * container (non-leaf) nodes:
  * The grayed state is used to visualize the checked state of its children.
  * Containers are checked and non-gray if all contained leafs are checked. The
  * container is grayed if some but not all leafs are checked.
  * @since 3.1
  */
 public class ContainerCheckedTreeViewer extends CheckboxTreeViewer {

     /**
      * Constructor for ContainerCheckedTreeViewer.
      * @see CheckboxTreeViewer#CheckboxTreeViewer(Composite)
      */
     public ContainerCheckedTreeViewer(Composite parent) {
         super(parent);
         initViewer();
     }

     /**
      * Constructor for ContainerCheckedTreeViewer.
      * @see CheckboxTreeViewer#CheckboxTreeViewer(Composite,int)
      */
     public ContainerCheckedTreeViewer(Composite parent, int style) {
         super(parent, style);
         initViewer();
     }

     /**
      * Constructor for ContainerCheckedTreeViewer.
      * @see CheckboxTreeViewer#CheckboxTreeViewer(Tree)
      */
     public ContainerCheckedTreeViewer(Tree tree) {
         super(tree);
         initViewer();
     }

     private void initViewer() {
         setUseHashlookup(true);
         addCheckStateListener(new ICheckStateListener() {
             public void checkStateChanged(CheckStateChangedEvent event) {
                 doCheckStateChanged(event.getElement());
             }
         });
         addTreeListener(new ITreeViewerListener() {
             public void treeCollapsed(TreeExpansionEvent event) {
             }

             public void treeExpanded(TreeExpansionEvent event) {
                 Widget item = findItem(event.getElement());
                 if (item instanceof TreeItem) {
                     initializeItem((TreeItem) item);
                 }
             }
         });
     }

     /**
      * Update element after a checkstate change.
      * @param element
      */
     protected void doCheckStateChanged(Object element) {
         Widget item = findItem(element);
         if (item instanceof TreeItem) {
             TreeItem treeItem = (TreeItem) item;
             treeItem.setGrayed(false);
             updateChildrenItems(treeItem);
             updateParentItems(treeItem.getParentItem());
         }
     }

     /**
      * The item has expanded. Updates the checked state of its children.
      */
     private void initializeItem(TreeItem item) {
         if (item.getChecked() && !item.getGrayed()) {
             updateChildrenItems(item);
         }
     }

     /**
      * Updates the check state of all created children
      */
     private void updateChildrenItems(TreeItem parent) {
         Item[] children = getChildren(parent);
         boolean state = parent.getChecked();
         for (int i = 0; i < children.length; i++) {
             TreeItem curr = (TreeItem) children[i];
             if (curr.getData() != null
                     && ((curr.getChecked() != state) || curr.getGrayed())) {
                 curr.setChecked(state);
                 curr.setGrayed(false);
                 updateChildrenItems(curr);
             }
         }
     }

     /**
      * Updates the check / gray state of all parent items
      */
     private void updateParentItems(TreeItem item) {
         if (item != null) {
             Item[] children = getChildren(item);
             boolean containsChecked = false;
             boolean containsUnchecked = false;
             for (int i = 0; i < children.length; i++) {
                 TreeItem curr = (TreeItem) children[i];
                 containsChecked |= curr.getChecked();
                 containsUnchecked |= (!curr.getChecked() || curr.getGrayed());
             }
             item.setChecked(containsChecked);
             item.setGrayed(containsChecked && containsUnchecked);
             updateParentItems(item.getParentItem());
         }
     }

     
     /* (non-Javadoc)
      * @see org.eclipse.jface.viewers.ICheckable#setChecked(java.lang.Object, boolean)
      */
     public boolean setChecked(Object element, boolean state) {
         if (super.setChecked(element, state)) {
             doCheckStateChanged(element);
             return true;
         }
         return false;
     }

  
     /* (non-Javadoc)
      * @see org.eclipse.jface.viewers.CheckboxTreeViewer#setCheckedElements(java.lang.Object[])
      */
     public void setCheckedElements(Object [] elements) {
         super.setCheckedElements(elements);
         for (int i = 0; i < elements.length; i++) {
             doCheckStateChanged(elements[i]);
         }
     }


     /* (non-Javadoc)
      * @see org.eclipse.jface.viewers.AbstractTreeViewer#setExpanded(org.eclipse.swt.widgets.Item, boolean)
      */
     protected void setExpanded(Item item, boolean expand) {
         super.setExpanded(item, expand);
         if (expand && item instanceof TreeItem) {
             initializeItem((TreeItem) item);
         }
     }

    
     /* (non-Javadoc)
      * @see org.eclipse.jface.viewers.CheckboxTreeViewer#getCheckedElements()
      */
     public Object [] getCheckedElements() {
         Object [] checked = super.getCheckedElements();
         // add all items that are children of a checked node but not created yet
 ArrayList result = new ArrayList ();
         for (int i = 0; i < checked.length; i++) {
             Object curr = checked[i];
             result.add(curr);
             Widget item = findItem(curr);
             if (item != null) {
                 Item[] children = getChildren(item);
                 // check if contains the dummy node
 if (children.length == 1 && children[0].getData() == null) {
                     // not yet created
 collectChildren(curr, result);
                 }
             }
         }
         return result.toArray();
     }

     /**
      * Recursively add the filtered children of element to the result.
      * @param element
      * @param result
      */
     private void collectChildren(Object element, ArrayList result) {
         Object [] filteredChildren = getFilteredChildren(element);
         for (int i = 0; i < filteredChildren.length; i++) {
             Object curr = filteredChildren[i];
             result.add(curr);
             collectChildren(curr, result);
         }
     }

 }

